/*
This is part of Screenbar, which is based in part on the Litestep
shell source code.

Copyright (C) 2008-2009 Jim Babcock
Copyright (C) 1997-2009 The LiteStep Development Team

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/
#include "stdafx.h"

void VWM::beginMovingWindows()
{
	movedWindows.clear();
}

void VWM::transferWindow(WindowData *window, DeskSlot *source, DeskSlot *dest)
{
	if(movedWindows.find(window) != movedWindows.end())
		movedWindows[window].second = dest;
	else
		movedWindows[window] = pair<DeskSlot*,DeskSlot*>(source, dest);
}

Rect VWM::transferRect(Rect rect, DeskSlot *source, DeskSlot *dest)
{
	if(source)
	{
		Rect result = rect;
		dest->transferFrom(result, source);
		return result;
	}
	else
	{
		return getGatherTarget(rect, dest);
	}
}

void VWM::finishMovingWindows()
{
	if(!movedWindows.size())
		return;
	
	HDWP windowMover = BeginDeferWindowPos(movedWindows.size());

	for(MovedWindowMap::iterator ii=movedWindows.begin(); ii!=movedWindows.end(); ii++)
	{
		WindowData *window = ii->first;
		DeskSlot *source = ii->second.first;
		DeskSlot *dest = ii->second.second;
		
		bool updatePlacement = false;
		WINDOWPLACEMENT placement;
		memset(&placement, 0, sizeof(placement));
		placement.length = sizeof(placement);
		if(!GetWindowPlacement(window->handle, &placement))
			continue;
		
		if(placement.showCmd == SW_SHOWMINIMIZED)
		{
			placement.rcNormalPosition = transferRect(placement.rcNormalPosition, source, dest);
			updatePlacement = true;
			SetWindowPlacement(window->handle, &placement);
		}
		else
		{
			Rect windowRect = window->getRect();
			Rect newPos = transferRect(windowRect, source, dest);
			
			bool sizeChanged = windowRect.getSize() != newPos.getSize();
			// Maximized windows need the position they'll restored to
			// adjusted, since we don't get a chance to set it when they
			// unmaximize.
			if(placement.showCmd == SW_MAXIMIZE)
			{
				VirtualDesktop *desk = getDeskFromWnd(window->handle);
				placement.ptMaxPosition.x = newPos.left;
				placement.ptMaxPosition.y = newPos.top;
				
				if(desk && desk->isVisible())
				{
					// Set the position the window will go to when restored
					// (unmaximized) to be on the correct monitor
					Rect restoredPos = placement.rcNormalPosition;
					
					vector<Monitor*> overlappingMonitors = monitors->findMonitor(restoredPos);
					if(overlappingMonitors.size())
					{
						Monitor *oldMonitor = overlappingMonitors[0];
						Monitor *newMonitor = desk->getMonitor();
						restoredPos = transferRect(restoredPos, oldMonitor, newMonitor);
						placement.rcNormalPosition = restoredPos;
					}
				}
				
				// Clear the window's clip region
				// This might break some apps, and it definitely causes window
				// borders to show accross monitors where they shouldn't, but
				// not doing this means that a maximized window which moves between
				// monitors becomes invisible.
				SetWindowRgn(window->handle, NULL, FALSE);
				
				SetWindowPlacement(window->handle, &placement);
				
				// Move
				SetWindowPos(window->handle, NULL,
				             newPos.left, newPos.top,
				             0, 0,
				             SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE);
				// Re-maximize
				SetWindowPos(window->handle, NULL,
				             newPos.left, newPos.top,
				             min(windowRect.width, newPos.width), min(windowRect.height, newPos.height),
				             SWP_NOMOVE|SWP_NOZORDER|SWP_NOACTIVATE);
			}
			else
			{
				DeferWindowPos(windowMover, window->handle, NULL,
				               newPos.left, newPos.top,
				               newPos.width, newPos.height,
				               SWP_NOZORDER|SWP_NOACTIVATE);
			}
			
			trace << "Moving window to " << newPos << "\n";
		}
	}

	if(!EndDeferWindowPos(windowMover))
		trace << "EndDeferWindowPos failed\n";

	movedWindows.clear();
}

VirtualDesktop *VWM::rescueOffscreenWindow(WindowData *window, Rect *pos)
{
	Monitor *targetMonitor = monitors->getPrimaryMonitor();
	
	if(!settings->rescueOffScreenWindows)
		return targetMonitor->getDesk();
	if(isWindowMinimized(window->handle, pos))
		return targetMonitor->getDesk();
	
	trace<<"Rescuing off-screen window from "<<*pos<<": "<<window->getTitle()<<".\n";
	*pos = getGatherTarget(*pos, targetMonitor);
	
	beginMovingWindows();
		transferWindow(window, NULL, targetMonitor);
	finishMovingWindows();
	
	return targetMonitor->getDesk();
}

void VWM::moveWindow(WindowData *window, VirtualDesktop *desk)
{
	if(desk == window->desk)
		return;
	
	transferWindow(window, window->desk->currentSlot(), desk->currentSlot());
	window->desk = desk;
}

void VWM::moveApp(WindowData *representative, VirtualDesktop *dest)
{
	if(!dest || !representative)
		return;
	
	TaskData *task = representative->task;
	VirtualDesktop *sourceDesk = representative->desk;
	
	// Find all the windows affected
	vector<WindowData*> movedWindows;
	task->getWindows(sourceDesk, movedWindows);
	
	// If the destination desk is already visible, or the source desk is
	// hidden, then we don't need to switch desk.
	if(dest->isVisible() || !sourceDesk->isVisible())
	{
		beginMovingWindows();
		for(unsigned ii=0; ii<movedWindows.size(); ii++)
			moveWindow(movedWindows[ii], dest);
		finishMovingWindows();
	}
	// Otherwise, we need to switch to showing the destination desk, without
	// moving the moved windows.
	else
	{
		for(unsigned ii=0; ii<movedWindows.size(); ii++)
			movedWindows[ii]->tempSticky = true;
		
		if(settings->switchOnFocus)
			switchDesk(dest, sourceDesk->getMonitor(), true);
		
		for(unsigned ii=0; ii<movedWindows.size(); ii++)
		{
			movedWindows[ii]->desk = dest;
			movedWindows[ii]->tempSticky = false;
		}
	}
}
